﻿/*
   Copyright 2013 Dmitry Bratus

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.Text;
using AopAlliance.Intercept;
using Flower.Services;
using Flower.Services.Data;
using Spring.Aop;
using Flower.Directory.Util;

namespace Flower.Directory.Host.Default.Advices
{
    internal class ArgumentsValidation : IMethodBeforeAdvice 
    {
        private class DirectoryEntryValidator : IDirectoryEntryVisitor
        {
            public void Visit(Assembly assembly)
            {
                if (assembly.Image == null) ThrowArgumentNull("Assembly.Image");
            }

            public void Visit(Folder folder)
            {
            }

            public void Visit(Link link)
            {
                if (link.Target == null) ThrowArgumentNull("Link.Target");
            }

            public void Visit(Message message)
            {
                if (message.Data != null && message.Data.Data == null) ThrowArgumentNull("Blob.Data");
            }

            public void Visit(Process process)
            {
                if (process.Workflow == null) ThrowArgumentNull("Process.Workflow");
            }

            public void Visit(Processor processor)
            {
                if (processor.Configuration != null && processor.Configuration.Data == null) ThrowArgumentNull("Blob.Data");
            }

            public void Visit(Role role)
            {
                if (role.Users == null) ThrowArgumentNull("Role.Users");
                if (role.Users.Any(u => u == null)) ThrowInvalidArgument("Role.Users contains nulls.");
            }

            public void Visit(ServiceContainer serviceContainer)
            {
                if (serviceContainer.Configuration == null) ThrowArgumentNull("ServiceContainer.Configuration");
                if (serviceContainer.Configuration.Data == null) ThrowArgumentNull("Blob.Data");
            }

            public void Visit(Set set)
            {
                if (set.MessageType == null) ThrowArgumentNull("Set.MessageType");
            }

            public void Visit(State state)
            {
                if (state.Data == null) ThrowArgumentNull("State.Data");
                if (state.Data.Data == null) ThrowArgumentNull("Blob.Data");
            }

            public void Visit(Workflow workflow)
            {
                if (workflow.DefinitionType == null) ThrowArgumentNull("Workflow.DefinitionType");
            }

            public void Visit(DirectoryEntryInfo directoryEntryInfo)
            {
            }
        }

        private static readonly Dictionary<string, Action<object[]>> Validations =
            new Dictionary<string, Action<object[]>> 
            {
                { 
                    "GetPathById", 
                    args => 
                    {
                        var id = args[0] as string;
                        if (string.IsNullOrEmpty(id)) ThrowArgumentNull("id");
                        if (!DirectoryUtil.IsId(id)) ThrowArgumentNull("id");
                    }
                }, 
                {
                    "Get", 
                    args => 
                    {
                        if (string.IsNullOrEmpty(args[0] as string)) ThrowArgumentNull("what");
                    }
                }, 
                {
                    "GetInfo", 
                    args => 
                    {
                        if (string.IsNullOrEmpty(args[0] as string)) ThrowArgumentNull("what");
                    }
                }, 
                {
                    "GetChildren", 
                    args => 
                    {
                        if (string.IsNullOrEmpty(args[0] as string)) ThrowArgumentNull("parent");
                        if (((long)args[4]) < 0) ThrowArgumentNull("offset");
                        if (((long)args[5]) < 0) ThrowArgumentNull("limit");
                    }
                }, 
                {
                    "GetChildrenInfo", 
                    args => 
                    {
                        if (string.IsNullOrEmpty(args[0] as string)) ThrowArgumentNull("parent");
                        if (((long)args[4]) < 0) ThrowArgumentNull("offset");
                        if (((long)args[5]) < 0) ThrowArgumentNull("limit");
                    }
                }, 
                {
                    "CountChildren", 
                    args => 
                    {
                        if (string.IsNullOrEmpty(args[0] as string)) ThrowArgumentNull("parent");
                    }
                }, 
                {
                    "GetAncestor", 
                    args => 
                    {
                        if (string.IsNullOrEmpty(args[0] as string)) ThrowArgumentNull("child");
                    }
                }, 
                {
                    "GetAncestorInfo", 
                    args => 
                    {
                        if (string.IsNullOrEmpty(args[0] as string)) ThrowArgumentNull("child");
                    }
                }, 
                {
                    "Find", 
                    args => 
                    {
                        if (string.IsNullOrEmpty(args[0] as string)) ThrowArgumentNull("what");
                    }
                }, 
                {
                    "Save",
                    args => 
                    {
                        var entry = args[1] as DirectoryEntry;
                        if (entry == null) ThrowArgumentNull("entry");

                        if (string.IsNullOrEmpty(args[0] as string) && entry.Id == null) ThrowArgumentNull("parent");
                        
                        if (entry.Id != null && !DirectoryUtil.IsId(entry.Id)) ThrowInvalidArgument("Entry id is invalid.");
                        if (entry.Name != null && !DirectoryUtil.IsValidDirectoryEntryName(entry.Name)) ThrowInvalidArgument("Entry name is invalid.");

                        entry.AcceptVisitor(new DirectoryEntryValidator());
                    }
                }, 
                {
                    "Move", 
                    args => 
                    {
                        if (string.IsNullOrEmpty(args[0] as string)) ThrowArgumentNull("what");
                        if (string.IsNullOrEmpty(args[1] as string)) ThrowArgumentNull("where");
                    }
                }, 
                {
                    "Remove", 
                    args => 
                    {
                        if (string.IsNullOrEmpty(args[0] as string)) ThrowArgumentNull("what");
                    }
                }, 
                {
                    "Exists",
                    args => 
                    {
                        if (string.IsNullOrEmpty(args[0] as string)) ThrowArgumentNull("what");
                    }
                },
                {
                    "ExecuteScript",
                    args => 
                    {
                        if (string.IsNullOrEmpty(args[0] as string)) ThrowArgumentNull("script");

                        var scriptArgs = args[1] as ICollection<ScriptParameter>;

                        if (scriptArgs == null) ThrowArgumentNull("args");
                        if (scriptArgs.Any(arg => arg == null)) ThrowInvalidArgument("Parameter args contains null.");
                        if (scriptArgs.Any(arg => string.IsNullOrEmpty(arg.Name))) ThrowInvalidArgument("Parameter name is null.");
                    }
                }
            };

        private static void ThrowArgumentNull(string argName)
        {
            throw new FaultException<DirectoryFault>
            (
                new DirectoryFault 
                {
                    Code = DirectoryFaultCode.InvalidArgument
                },
                string.Format("Argument {0} is null.", argName)
            );
        }

        private static void ThrowInvalidArgument(string reason)
        {
            throw new FaultException<DirectoryFault>
            (
                new DirectoryFault
                {
                    Code = DirectoryFaultCode.InvalidArgument
                },
                reason
            );
        }

        public void Before(System.Reflection.MethodInfo method, object[] args, object target)
        {
            Action<object[]> validation;

            if (Validations.TryGetValue(method.Name, out validation))
            {
                validation(args);
            }
        }
    }
}
